local timer = require("timer")
local Websocket = require("luv-http.websocket")

local quotepattern = '(['..("%^$().[]*+-?"):gsub("(.)", "%%%1")..'])'

local function escape(str)
    return str:gsub(quotepattern, "%%%1")
end

local function compileRoute(route)
  local parts = {"^"}
  local names = {}
  for a, b, c, d in route:gmatch("([^:]*):([_%a][_%w]*)(:?)([^:]*)") do
    if #a > 0 then
      parts[#parts + 1] = escape(a)
    end
    if #c > 0 then
      parts[#parts + 1] = "(.*)"
    else
      parts[#parts + 1] = "([^/]*)"
    end
    names[#names + 1] = b
    if #d > 0 then
      parts[#parts + 1] = escape(d)
    end
  end
  if #parts == 1 then
    return function (string)
      if string == route then return {} end
    end
  end
  parts[#parts + 1] = "$"
  local pattern = table.concat(parts)
  return function (string)
    local matches = {string:match(pattern)}
    if #matches > 0 then
      local results = {}
      for i = 1, #matches do
        results[i] = matches[i]
        results[names[i]] = matches[i]
      end
      return results
    end
  end
end

local isWsReq = function (req)
    local isWebsocket = true
    isWebsocket = isWebsocket and ("websocket" == req.headers["Upgrade"])
    isWebsocket = isWebsocket and ("Upgrade" == req.headers["Connection"])
    isWebsocket = isWebsocket and (nil ~= req.headers["Sec-WebSocket-Key"])
    isWebsocket = isWebsocket and ("13" == req.headers["Sec-WebSocket-Version"])
    return isWebsocket
end

local continueWebsocket = function (resp, SecWebSocketKey)
    resp.statusCode = 101 -- 保持连接
    resp:setHeader("Upgrade", "websocket")
    resp:setHeader("Connection", "Upgrade")
    resp:setHeader("Sec-WebSocket-Accept", Websocket.sha1(SecWebSocketKey .. "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"))
end

local isMethodMatch = function (method, route)
    local methodMatch = false
    if "string" == type(route.method) then
        methodMatch = (string.lower(route.method) == string.lower(method))
    end
    if "table" == type(route.method) then
        for _,item in ipairs(route.method) do
            if string.lower(item) == string.lower(method) then
                methodMatch = true
                break
            end
        end
    end
    return methodMatch
end

local startHeartBeat
startHeartBeat = function (heartBeat, isOverTime, interval, close)
    timer.setTimeout(interval, function ()
        if isOverTime() then
            close()
        else
            heartBeat()
            startHeartBeat(heartBeat, isOverTime, interval, close)
        end
    end)
end

local getHandlers = function (ctx, routes, isWebsocket)
    if not routes then
        routes = {}
    end
    local handlers = {}
    for index,route in ipairs(routes) do
        local clazz = require(route.handler)
        local inst = clazz.new(ctx, route.initParam)
        local handler = {}
        handler.match = function (req, resp, ctx)
            local requesturi, querystring = req.url:match("^([^?]*)%??(.*)")
            local getParams = compileRoute(route.path)
            local params = getParams(requesturi)
            if isWsReq(req) then
                return isWebsocket and nil ~= params --如果为空，则路径配置不匹配
            else
                if not isMethodMatch(req.method, route) then
                    --请求方法不匹配，直接返回
                    return false
                end
                req.params = params or {}
                return nil ~= params --如果为空，则路径配置不匹配
            end
        end
        handler.handle = function (req, resp, ctx)
            if isWebsocket then
                local thread = coroutine.running()
                local lowerSend = function (data)
                    resp:write(data)
                end
                local ws = {}
                ws.send = function (text)
                    Websocket.sendFrame(lowerSend, "text", text, true)
                end
                local isEnd = false
                ws.close = function ()
                    if not isEnd then
                        Websocket.sendFrame(lowerSend, "close")
                        isEnd = true
                    end
                end
                local evts = inst(ws, req, ctx)
                local extensionDataLength = 0
                --获取扩展数据长度
                local SecWebSocketExtension = req.headers["Sec-WebSocket-Extension"]
                if SecWebSocketExtension then
                    extensionDataLength = tonumber(SecWebSocketExtension)
                end
                local sentCount = 0
                local ping = function ()
                    Websocket.sendFrame(lowerSend, "ping")
                    sentCount = sentCount + 1
                end
                local pong = function ()
                    Websocket.sendFrame(lowerSend, "pong")
                end
                local isOverTime = function ()
                    --3次未收到应答就算超时
                    return sentCount >= 3 or isEnd
                end
                local closeWebsocket = function ()
                    ws.close()
                end
                local onTrunk = Websocket.new(extensionDataLength, function (typ, data)
                    if "continuation" == typ then
                        if evts and evts.onEvent then
                            evts.onEvent("continuation")
                        end
                    elseif "text" == typ then
                        if evts and evts.onMessage then
                            evts.onMessage(data)
                        end
                    elseif "binary" == typ then
                        if evts and evts.onBinary then
                            evts.onBinary(data)
                        end
                    elseif "close" == typ then
                        isEnd = true
                        if evts and evts.onClose then
                            evts.onClose()
                        end
                        resp:_end()
                    elseif "ping" == typ then
                        --接收到客户端的ping消息必须响应
                        pong()
                        if evts and evts.onEvent then
                            evts.onEvent("ping")
                        end
                    elseif "pong" == typ then
                        sentCount = sentCount - 1
                        if evts and evts.onEvent then
                            evts.onEvent("pong")
                        end
                    else
                    end
                end)
                --遇到协议升级的请求data和end事件监听器被移除了，需要重新在socket上绑定事件
                req.socket:on("data", onTrunk)
                req.socket:on("end", function()
                    coroutine.resume(thread)
                end)
                continueWebsocket(resp, req.headers["Sec-WebSocket-Key"])
                --发送响应头儿，让客户端知晓
                resp:flushHeaders()
                if evts and evts.onOpen then
                    evts.onOpen()
                end
                --开启心跳，30秒1次
                startHeartBeat(ping, isOverTime, 30 * 1000, closeWebsocket)
                coroutine.yield()
            else
                inst(req, resp, ctx)
            end
        end
        table.insert(handlers, handler)
    end
    return handlers
end

local moduleName = ...
_G[moduleName] = getHandlers
complex = getHandlers
return complex